﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace Microsoft.Goldenlight.Framework
{
    /// <summary>
    /// 	Base class which provides registration capability.
    /// </summary>
    /// <remarks>
    /// 	RegisterableObject can be looked up in a ObjectManager by id.
    /// </remarks>
    public class RegisterableObject : LifetimeObject
    {
        #region Fields & Properties

        private uint _id;

        /// <summary>
        /// 	Integer id of this object assigned by ObjectManager.
        /// </summary>
        [CloneIgnore]
        public uint Id
        {
            get
            {
                return _id;
            }
            internal set
            {
                _id = value;
            }
        }

        private ObjectManager _manager;

        /// <summary>
        /// 	The ObjectManager which owns this object.
        /// </summary>
        [CloneIgnore]
        public ObjectManager Manager
        {
            get
            {
                return _manager;
            }
            internal set
            {
                _manager = value;
            }
        }

        #endregion

        #region Status Fields & Properties

        private bool _isTemplate;

        /// <summary>
        /// 	True if this object is a template.
        /// </summary>
        /// <remarks>
        /// 	Templates cannot be registered with a object manager, but must be cloned and the clone added.
        /// </remarks>
        [CloneIgnore]
        public bool IsTemplate
        {
            get
            {
                return _isTemplate;
            }
            set
            {
                _isTemplate = value;
            }
        }

        private bool _isMarkedForDelete;

        /// <summary>
        /// 	Set to true if you want the object unregistered in short order.
        /// </summary>
        [CloneIgnore]
        public bool IsMarkedForDelete
        {
            get
            {
                return _isMarkedForDelete;
            }
            set
            {
                if (_manager != null && _isMarkedForDelete != value)
                {
                    if (value)
                    {
                        _manager.AddMarkedObject(this);
                    }
                    else
                    {
                        _manager.RemoveMarkedObject(this);
                    }
                    _isMarkedForDelete = value;
                }
            }
        }

        #endregion

        #region Overrides of LifetimeObject

        #region Implementation of ICloneable

        /// <summary>
        /// 	Called by Clone. Objects need to implement this method to copy all public properties not marked with TorqueCloneIgnore attribute.
        /// </summary>
        /// <param name="newLifetimeObject"> The object to be copied over. </param>
        public override void CopyTo(LifetimeObject newLifetimeObject)
        {
            base.CopyTo(newLifetimeObject);
            var newRegisterableObject = newLifetimeObject as RegisterableObject;
            if (newRegisterableObject != null)
            {
                foreach (var key in _values.Keys)
                {
                    newRegisterableObject._values[key] = _values[key];
                }
            }
        }

        #endregion

        #region Implementation of IDisposable

        /// <summary>
        /// 	Releases all resources used by the object class.
        /// </summary>
        /// <param name="disposing"> true to release both managed and unmanaged resources; false to release only unmanaged resources. </param>
        protected override void Dispose(bool disposing)
        {
            _isDisposed = true;
            if (disposing)
            {
                if (_manager != null)
                {
                    _manager.Unregister(this, false);
                }
                ClearValues(false);
                _id = 0;
                _manager = null;
                _isTemplate = false;
                _isMarkedForDelete = false;
            }
            base.Dispose(disposing);
        }

        #endregion

        #endregion

        #region Registration Operations

        #region Fields & Properties

        /// <summary>
        /// 	Gets the current registration state of the registration object.
        /// </summary>
        public bool IsRegistered
        {
            get
            {
                return _manager != null;
            }
        }

        #endregion

        #region Events

        /// <summary>
        /// 	Occurs when the lifetime object completes its transition from the initialized state into the registered state.
        /// </summary>
        public event EventHandler<EventArgs> Registered;

        /// <summary>
        /// 	Invoked during the transition of a lifetime object into the registered state.
        /// </summary>
        protected virtual void OnRegistered()
        {
            var onRegistered = Registered;
            if (onRegistered != null)
            {
                onRegistered(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// 	Occurs when the lifetime object completes its transition from the registered state into the unregistered state.
        /// </summary>
        public event EventHandler<EventArgs> Unregistered;

        /// <summary>
        /// 	Invoked during the transition of a lifetime object into the unregistering state.
        /// </summary>
        protected virtual void OnUnregistered()
        {
            var onUnregistered = Unregistered;
            if (onUnregistered != null)
            {
                onUnregistered(this, EventArgs.Empty);
            }
        }

        #endregion

        #region Register & Unregister

        /// <summary>
        /// 	Inserts processing on a lifetime object after it transitions into the registering state which must complete within a specified interval of time.
        /// </summary>
        protected virtual void OnRegister()
        {
        }

        /// <summary>
        /// 	Inserts processing on a lifetime object after it transitions to the unregistering state due to the invocation of a synchronous unregister operation.
        /// </summary>
        protected virtual void OnUnregister()
        {
        }

        /// <summary>
        /// 	Causes a lifetime object to transition from the initialized state into the registered state.
        /// </summary>
        internal bool Register()
        {
            try
            {
                OnRegister();
                OnRegistered();
                return true;
            }
            catch (Exception ex)
            {
                //TODO: Trace
                Debug.WriteLine(ex.Message);
                return false;
            }
        }

        /// <summary>
        /// 	Causes a lifetime object to transition from its current registration state into the unregistered state.
        /// </summary>
        internal void Unregister()
        {
            try
            {
                OnUnregister();
                OnUnregistered();
            }
            catch (Exception ex)
            {
                //TODO: Trace
                Debug.WriteLine(ex.Message);
            }
        }

        #endregion

        #endregion

        #region Value Operations

        #region Collection Fields & Properties

        private readonly Dictionary<string, object> _values = new Dictionary<string, object>();

        #endregion

        #region Get & Set

        /// <summary>
        /// 	Get value stored on this object for the given key.
        /// </summary>
        /// <typeparam name="T"> The type of the value. </typeparam>
        /// <param name="key"> Key under which value is stored. </param>
        /// <returns> Value stored on this object </returns>
        public T GetValue<T>(string key)
        {
            T value;
            return GetValue(key, out value) ? value : default(T);
        }

        /// <summary>
        /// 	Get value stored on this object for the given key.
        /// </summary>
        /// <typeparam name="T"> The type of the value. </typeparam>
        /// <param name="key"> Key under which value is stored. </param>
        /// <param name="value"> Value stored on this object </param>
        /// <returns> </returns>
        public bool GetValue<T>(string key, out T value)
        {
            if (string.IsNullOrEmpty(key))
            {
                throw new ArgumentNullException("key");
            }
            object current;
            if (_values.TryGetValue(key, out current) && current is T)
            {
                value = (T) current;
                return true;
            }
            value = default(T);
            return false;
        }

        /// <summary>
        /// 	Set value on this object for the given key.
        /// </summary>
        /// <typeparam name="T"> The type of the value. </typeparam>
        /// <param name="key"> Key under which value is stored. </param>
        /// <param name="value"> Value stored on this object </param>
        public void SetValue<T>(string key, T value)
        {
            if (string.IsNullOrEmpty(key))
            {
                throw new ArgumentNullException("key");
            }
            _values[key] = value;
        }

        #endregion

        #region Clear

        /// <summary>
        /// 	Remove all values from the game object.
        /// </summary>
        /// <param name="dispose"> Dispose an object will perform any cleanup that is needed and will clear. </param>
        public void ClearValues(bool dispose)
        {
            if (dispose)
            {
                var values = _values.Values.ToArray();
                foreach (var disposable in values.Where(obj => obj != null).OfType<IDisposable>())
                {
                    disposable.Dispose();
                }
            }
            _values.Clear();
        }

        #endregion

        #endregion
    }
}